package com.stottlerhenke.simbionic.engine.parser;
import com.stottlerhenke.simbionic.engine.core.*;
import com.stottlerhenke.simbionic.engine.SB_SimInterface;
import com.stottlerhenke.simbionic.api.*;
import com.stottlerhenke.simbionic.common.EErrCode;
import com.stottlerhenke.simbionic.common.parser.EBinaryOp;
import com.stottlerhenke.simbionic.common.parser.SB_ExpressionNode;
import com.stottlerhenke.simbionic.engine.SB_SingletonBook;

import java.lang.reflect.*;


public class SB_ExpBinaryOp extends SB_Expression {
  protected SB_Expression _arg1;
  protected SB_Expression _arg2;
  protected EBinaryOp _op;

  public SB_ExpBinaryOp(SB_ExpressionNode arg1, SB_ExpressionNode arg2, EBinaryOp op) {
    super();
    _arg1 = (SB_Expression)arg1;
    _arg2 = (SB_Expression)arg2;
    _op = op;
  }
  public EExpressionType GetType() { return EExpressionType.kBinary; }
  
  /**
   * Replaces all instances of SB_Method in the expression tree that
   * match the name of newMethod.
   * @param newMethod the method with which to replace existing methods
   */
  public void replaceFunction(SB_Function newMethod)
  {
      _arg1.replaceFunction(newMethod);
      _arg2.replaceFunction(newMethod);
  }
  
  public SB_Variable Evaluate(SB_Entity entity, SB_ExecutionFrame contextFrame) throws SB_Exception 
  {
    SB_Variable result;
    SB_Variable arg1 = _arg1.Evaluate(entity, contextFrame);
    
    if (arg1 == null)
    {
        throw new SB_Exception("Left-hand argument of expression '" + PrintTree() + "' is null.");
    }  
    
    //If arg1 is a nested call, then convert it to kClass from kVariable
    if( _arg2 instanceof SB_ExpMethod && !(arg1 instanceof SB_VarClass))
    {
      SB_VarClass temp = new SB_VarClass();
      temp.setClassObject(arg1.getSB_Param().getData());
      arg1 = temp;
    }
    
    SB_Variable arg2 = _arg2.Evaluate(entity, contextFrame);
    
    if ((arg1 == null) || (arg2 == null))
    {
        throw new SB_Exception(((arg1 == null) ? "Left" : "Right") + "-hand argument to expression '" + PrintTree() + "' is null.");
    }  
    
    if(_op == EBinaryOp.kAddOp)
      result = arg1.opADD(arg2);
    else if(_op == EBinaryOp.kSubOp)
      result = arg1.opSUB(arg2);
    else if(_op == EBinaryOp.kMultOp)
      result = arg1.opMUL(arg2);
    else if(_op == EBinaryOp.kDivOp)
      result = arg1.opDIV(arg2);
    else if(_op == EBinaryOp.kLssOp)
      result = arg1.opLSS(arg2);
    else if(_op == EBinaryOp.kLeqOp)
      result = arg1.opLSSEq(arg2);
    else if(_op == EBinaryOp.kEqlOp)
      result = arg1.equals(arg2);
    else if(_op == EBinaryOp.kNeqOp)
      result = arg1.opNOTEq(arg2);
    else if(_op == EBinaryOp.kGrtOp)
      result = arg1.opGRT(arg2);
    else if(_op == EBinaryOp.kGeqOp)
      result = arg1.opGRTEq(arg2);
    else if(_op == EBinaryOp.kModOp)
      result = arg1.opMOD(arg2);
    else if(_op == EBinaryOp.kExpOp)
      result = arg1.opEXP(arg2);
    else if(_op == EBinaryOp.kAndOp)
      result = arg1.opAND(arg2);
    else if(_op == EBinaryOp.kOrOp)
      result = arg1.opOr(arg2);
    else if(_op == EBinaryOp.kDotOp)
    {
    	result = EvaluateDotOp(entity, contextFrame, arg1, arg2);
    }
    else return null;

    return result;
  }

  public String PrintTree() throws SB_Exception{
    String out = new String("(");
    out = out + _arg1.PrintTree();
    if(_op == EBinaryOp.kAddOp)
      out = out + " + ";
    else if(_op == EBinaryOp.kSubOp)
      out = out + " - ";
    else if(_op == EBinaryOp.kMultOp)
      out = out + " * ";
    else if(_op == EBinaryOp.kDivOp)
      out = out + " / ";
    else if(_op == EBinaryOp.kModOp)
      out = out + " % ";
    else if(_op == EBinaryOp.kExpOp)
      out = out + " ^ ";
    else if(_op == EBinaryOp.kLssOp)
      out = out + " < ";
    else if(_op == EBinaryOp.kLeqOp)
      out = out + " <= ";
    else if(_op == EBinaryOp.kGrtOp)
      out = out + " > ";
    else if(_op == EBinaryOp.kEqlOp)
      out = out + " == ";
    else if(_op == EBinaryOp.kNeqOp)
      out = out + " <> ";
    else if(_op == EBinaryOp.kGeqOp)
      out = out + " >= ";
    else if(_op == EBinaryOp.kAndOp)
      out = out + " && ";
    else if(_op == EBinaryOp.kOrOp)
      out = out + " || ";

    out = out + _arg2.PrintTree();

    return out + ")";
  }

  /**
   *  Evaluate an expression with a dot operator. This could be a vector or a class member.
   * @param arg1 The vector or class
   * @param arg2 The member
   * @return null if member not found, the value of the member otherwise
   */
  protected SB_Variable EvaluateDotOp(SB_Entity entity, SB_ExecutionFrame contextFrame, SB_Variable arg1, SB_Variable arg2)
  	throws SB_Exception
  {
  	SB_Variable result = null;
  	
    SB_VarType arg1Type = arg1.GetType();
    
    if(arg1Type == SB_VarType.kVector)
    {
    	result = EvaluateVectorMember(arg1, arg2);
    }
    else
    if( arg1Type.getState() > SB_VarType.kCLASS_START.getState() &&
    		arg2.GetType().getState() == SB_VarType.kData.getState()) //Member method
    {
    	result = EvaluateClassMethod(entity, contextFrame, (SB_VarClass) arg1, arg2);
    }
    else
    if(arg1Type.getState() > SB_VarType.kCLASS_START.getState()) //Member variable
    {
    	result = EvaluateClassMember(arg1, arg2, contextFrame.GetBook());
    }
    
    return result;
  }
  
  /**
   * Evaluate a vector member
	 */
  protected SB_Variable EvaluateVectorMember(SB_Variable arg1, SB_Variable arg2)
  throws SB_Exception
  {
  	SB_Variable result = null;
 
  	String tmpStr;
  	tmpStr = SB_VarString.GET_STRING(arg2);
    if(tmpStr.charAt(0) == 'x')
      result = new SB_VarFloat(SB_VarVector.GET_VECTOR(arg1).x);
    else if(tmpStr.charAt(0) == 'y')
      result = new SB_VarFloat(SB_VarVector.GET_VECTOR(arg1).y);
    else if(tmpStr.charAt(0) == 'z')
      result = new SB_VarFloat(SB_VarVector.GET_VECTOR(arg1).z);
    
    return result;
  }
  
  protected SB_Variable EvaluateClassMethod(SB_Entity entity, SB_ExecutionFrame contextFrame, SB_VarClass arg1, SB_Variable arg2)
  	throws SB_Exception
  {
  	return ((SB_ExpMethod) arg2.getSB_Param().getData()).Evaluate(entity, contextFrame, arg1);
  }
  
  /**
   * Evaluate a class member by extracting its value
   * @todo
   */
  protected SB_Variable EvaluateClassMember(SB_Variable arg1, SB_Variable arg2, SB_SingletonBook book)
  	throws SB_Exception
  {
  	SB_Variable result = null;
  	
  	Object obj = ((SB_VarClass)arg1).getClassObject();
   	Class cls = ((SB_VarClass)arg1).getClassClass();
  	
  	result = EvaluateClassMemberField(cls, obj, arg2.toString(), book);
 
    return result;
  }
  
  /**
   * @return null if a public field of the same name does not exist, the value otherwise
   */
  protected SB_Variable EvaluateClassMemberField(Class cls, Object obj, String fieldName, SB_SingletonBook book)
  	throws SB_Exception
  {
  	SB_Variable result = null;
  	
  	try
  	{
  	  Field f = cls.getDeclaredField(fieldName);
  	  Object o = f.get(obj);
   	  SB_Param p = SB_SimInterface.ConvertObjectToSB_Param(o);
   	  result = SB_SimInterface.ConvertParamAuto(p, book);
		}
  	catch(NoSuchFieldException ex)
  	{
  	  return result;
  	}
   	catch(Exception ex)
		{
   		throw new SB_Exception("Failed to evaluate class member field ( "
   		    + cls.getName() + "." + fieldName + " ); "
   		    + ex.toString(), EErrCode.kE_ERROR);
		}
 
  	return result;
  }
}